Do not allow the cursor to go to a separator row. Fixes #441219, modified
authorKristian Rietveld <kris@imendio.com>
Wed, 22 Aug 2007 14:26:32 +0000 (14:26 +0000)
committerKristian Rietveld <kristian@src.gnome.org>
Wed, 22 Aug 2007 14:26:32 +0000 (14:26 +0000)
2007-08-22  Kristian Rietveld  <kris@imendio.com>

Do not allow the cursor to go to a separator row.  Fixes #441219,
modified patch by Carlos Garnacho.

* gtk/gtktreeview.c (row_is_separator): new inline function
for calling the row-separator-func, made all of gtktreeview.c use
this function instead of calling the func themselves,
(gtk_tree_view_button_press): treat a click on a separator like
a click on empty space,
(gtk_tree_view_update_rubber_band_selection_rage): skip
non-selectable rows,
(search_first_focusable_path),
(gtk_tree_view_focus_to_cursor),
(gtk_tree_view_move_cursor_up_down),
(gtk_tree_view_move_cursor_page_up_down),
(gtk_tree_view_move_cursor_start_end): skip separators,
(gtk_tree_view_real_set_cursor): do not allow setting the cursor
on a separator.

svn path=/trunk/; revision=18672

ChangeLog
gtk/gtktreeview.c

index 49ff1636660ec07b60ba10b12061c5e8fdac7f57..d36b2af0b07b6066bde057c08c0a77ac0c3bf3b1 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,23 @@
+2007-08-22  Kristian Rietveld  <kris@imendio.com>
+
+       Do not allow the cursor to go to a separator row.  Fixes #441219,
+       modified patch by Carlos Garnacho.
+
+       * gtk/gtktreeview.c (row_is_separator): new inline function
+       for calling the row-separator-func, made all of gtktreeview.c use
+       this function instead of calling the func themselves,
+       (gtk_tree_view_button_press): treat a click on a separator like
+       a click on empty space,
+       (gtk_tree_view_update_rubber_band_selection_rage): skip
+       non-selectable rows,
+       (search_first_focusable_path),
+       (gtk_tree_view_focus_to_cursor),
+       (gtk_tree_view_move_cursor_up_down),
+       (gtk_tree_view_move_cursor_page_up_down),
+       (gtk_tree_view_move_cursor_start_end): skip separators,
+       (gtk_tree_view_real_set_cursor): do not allow setting the cursor
+       on a separator.
+
 2007-08-22  Tristan Van Berkom  <tvb@gnome.org>
 
        * gtk/gtkframe.c: Corrected the published default value of
index 3812a37e5b3b9b6a62b882122ae56ba72d75c48c..8e21633cba2979bc1870728416c4dfe4d2c5caa9 100644 (file)
@@ -2433,6 +2433,30 @@ grab_focus_and_unset_draw_keyfocus (GtkTreeView *tree_view)
   GTK_TREE_VIEW_UNSET_FLAG (tree_view, GTK_TREE_VIEW_DRAW_KEYFOCUS);
 }
 
+static inline gboolean
+row_is_separator (GtkTreeView *tree_view,
+                 GtkTreeIter *iter,
+                 GtkTreePath *path)
+{
+  gboolean is_separator = FALSE;
+
+  if (tree_view->priv->row_separator_func)
+    {
+      GtkTreeIter tmpiter;
+
+      if (iter)
+       tmpiter = *iter;
+      else
+       gtk_tree_model_get_iter (tree_view->priv->model, &tmpiter, path);
+
+      is_separator = (* tree_view->priv->row_separator_func) (tree_view->priv->model,
+                                                             &tmpiter,
+                                                             tree_view->priv->row_separator_data);
+    }
+
+  return is_separator;
+}
+
 static gboolean
 gtk_tree_view_button_press (GtkWidget      *widget,
                            GdkEventButton *event)
@@ -2445,6 +2469,7 @@ gtk_tree_view_button_press (GtkWidget      *widget,
   GdkRectangle cell_area;
   gint vertical_separator;
   gint horizontal_separator;
+  gboolean path_is_selectable;
   gboolean rtl;
 
   g_return_val_if_fail (GTK_IS_TREE_VIEW (widget), FALSE);
@@ -2524,6 +2549,15 @@ gtk_tree_view_button_press (GtkWidget      *widget,
 
       /* Get the path and the node */
       path = _gtk_tree_view_find_path (tree_view, tree, node);
+      path_is_selectable = !row_is_separator (tree_view, NULL, path);
+
+      if (!path_is_selectable)
+       {
+         gtk_tree_path_free (path);
+         grab_focus_and_unset_draw_keyfocus (tree_view);
+         return TRUE;
+       }
+
       depth = gtk_tree_path_get_depth (path);
       background_area.y = y_offset + event->y;
       background_area.height = ROW_HEIGHT (tree_view, GTK_RBNODE_GET_HEIGHT (node));
@@ -3696,6 +3730,22 @@ gtk_tree_view_update_rubber_band_selection_range (GtkTreeView *tree_view,
 
   do
     {
+      /* Small optimization by assuming insensitive nodes are never
+       * selected.
+       */
+      if (!GTK_RBNODE_FLAG_SET (start_node, GTK_RBNODE_IS_SELECTED))
+        {
+         GtkTreePath *path;
+         gboolean selectable;
+
+         path = _gtk_tree_view_find_path (tree_view, start_tree, start_node);
+         selectable = _gtk_tree_selection_row_is_selectable (tree_view->priv->selection, start_node, path);
+         gtk_tree_path_free (path);
+
+         if (!selectable)
+           goto node_not_selectable;
+       }
+
       if (select)
         {
          if (tree_view->priv->rubber_band_shift)
@@ -3730,6 +3780,7 @@ gtk_tree_view_update_rubber_band_selection_range (GtkTreeView *tree_view,
 
       _gtk_tree_view_queue_draw_node (tree_view, start_tree, start_node, NULL);
 
+node_not_selectable:
       if (start_node == end_node)
        break;
 
@@ -4274,12 +4325,7 @@ gtk_tree_view_bin_expose (GtkWidget      *widget,
       gboolean is_first = FALSE;
       gboolean is_last = FALSE;
       
-      if (tree_view->priv->row_separator_func)
-       {
-         is_separator = (* tree_view->priv->row_separator_func) (tree_view->priv->model,
-                                                                 &iter,
-                                                                 tree_view->priv->row_separator_data);
-       }
+      is_separator = row_is_separator (tree_view, &iter, NULL);
 
       max_height = ROW_HEIGHT (tree_view, BACKGROUND_HEIGHT (node));
 
@@ -5519,12 +5565,7 @@ validate_row (GtkTreeView *tree_view,
       ! GTK_RBNODE_FLAG_SET (node, GTK_RBNODE_COLUMN_INVALID))
     return FALSE;
 
-  if (tree_view->priv->row_separator_func)
-    {
-      is_separator = (* tree_view->priv->row_separator_func) (tree_view->priv->model,
-                                                             iter,
-                                                             tree_view->priv->row_separator_data);
-    }
+  is_separator = row_is_separator (tree_view, iter, NULL);
 
   gtk_widget_style_get (GTK_WIDGET (tree_view),
                        "focus-padding", &focus_pad,
@@ -7746,6 +7787,52 @@ gtk_tree_view_header_focus (GtkTreeView      *tree_view,
   return (focus_child != NULL);
 }
 
+/* This function returns in 'path' the first focusable path, if the given path
+ * is already focusable, it's the returned one.
+ */
+static gboolean
+search_first_focusable_path (GtkTreeView  *tree_view,
+                            GtkTreePath **path,
+                            gboolean      search_forward,
+                            GtkRBTree   **new_tree,
+                            GtkRBNode   **new_node)
+{
+  GtkRBTree *tree = NULL;
+  GtkRBNode *node = NULL;
+
+  if (!path || !*path)
+    return FALSE;
+
+  _gtk_tree_view_find_node (tree_view, *path, &tree, &node);
+
+  if (!tree || !node)
+    return FALSE;
+
+  while (node && row_is_separator (tree_view, NULL, *path))
+    {
+      if (search_forward)
+       _gtk_rbtree_next_full (tree, node, &tree, &node);
+      else
+       _gtk_rbtree_prev_full (tree, node, &tree, &node);
+
+      if (*path)
+       gtk_tree_path_free (*path);
+
+      if (node)
+       *path = _gtk_tree_view_find_path (tree_view, tree, node);
+      else
+       *path = NULL;
+    }
+
+  if (new_tree)
+    *new_tree = tree;
+
+  if (new_node)
+    *new_node = node;
+
+  return (*path != NULL);
+}
+
 static gint
 gtk_tree_view_focus (GtkWidget        *widget,
                     GtkDirectionType  direction)
@@ -9496,21 +9583,22 @@ gtk_tree_view_focus_to_cursor (GtkTreeView *tree_view)
       (! GTK_WIDGET_REALIZED (tree_view)))
     return;
 
-  GTK_TREE_VIEW_SET_FLAG (tree_view, GTK_TREE_VIEW_DRAW_KEYFOCUS);
-
   cursor_path = NULL;
   if (tree_view->priv->cursor)
     cursor_path = gtk_tree_row_reference_get_path (tree_view->priv->cursor);
 
   if (cursor_path == NULL)
     {
-      /* Consult the selection before defaulting to the first element */
+      /* Consult the selection before defaulting to the
+       * first focusable element
+       */
+      GList *selected_rows;
+      GtkTreeModel *model;
       GtkTreeSelection *selection;
-      GtkTreeModel     *model;
-      GList            *selected_rows;
 
       selection = gtk_tree_view_get_selection (tree_view);
       selected_rows = gtk_tree_selection_get_selected_rows (selection, &model);
+
       if (selected_rows)
        {
           cursor_path = gtk_tree_path_copy((const GtkTreePath *)(selected_rows->data));
@@ -9518,28 +9606,41 @@ gtk_tree_view_focus_to_cursor (GtkTreeView *tree_view)
          g_list_free (selected_rows);
         }
       else
-       cursor_path = gtk_tree_path_new_first ();
+       {
+         cursor_path = gtk_tree_path_new_first ();
+         search_first_focusable_path (tree_view, &cursor_path,
+                                      TRUE, NULL, NULL);
+       }
 
       gtk_tree_row_reference_free (tree_view->priv->cursor);
       tree_view->priv->cursor = NULL;
 
-      if (tree_view->priv->selection->type == GTK_SELECTION_MULTIPLE)
-       gtk_tree_view_real_set_cursor (tree_view, cursor_path, FALSE, FALSE);
-      else
-       gtk_tree_view_real_set_cursor (tree_view, cursor_path, TRUE, FALSE);
+      if (cursor_path)
+       {
+         if (tree_view->priv->selection->type == GTK_SELECTION_MULTIPLE)
+           gtk_tree_view_real_set_cursor (tree_view, cursor_path, FALSE, FALSE);
+         else
+           gtk_tree_view_real_set_cursor (tree_view, cursor_path, TRUE, FALSE);
+       }
     }
-  gtk_tree_view_queue_draw_path (tree_view, cursor_path, NULL);
-  gtk_tree_path_free (cursor_path);
 
-  if (tree_view->priv->focus_column == NULL)
+  if (cursor_path)
     {
-      GList *list;
-      for (list = tree_view->priv->columns; list; list = list->next)
+      GTK_TREE_VIEW_SET_FLAG (tree_view, GTK_TREE_VIEW_DRAW_KEYFOCUS);
+
+      gtk_tree_view_queue_draw_path (tree_view, cursor_path, NULL);
+      gtk_tree_path_free (cursor_path);
+
+      if (tree_view->priv->focus_column == NULL)
        {
-         if (GTK_TREE_VIEW_COLUMN (list->data)->visible)
+         GList *list;
+         for (list = tree_view->priv->columns; list; list = list->next)
            {
-             tree_view->priv->focus_column = GTK_TREE_VIEW_COLUMN (list->data);
-             break;
+             if (GTK_TREE_VIEW_COLUMN (list->data)->visible)
+               {
+                 tree_view->priv->focus_column = GTK_TREE_VIEW_COLUMN (list->data);
+                 break;
+               }
            }
        }
     }
@@ -9594,6 +9695,20 @@ gtk_tree_view_move_cursor_up_down (GtkTreeView *tree_view,
                               &new_cursor_tree, &new_cursor_node);
     }
 
+  if (new_cursor_node)
+    {
+      cursor_path = _gtk_tree_view_find_path (tree_view,
+                                             new_cursor_tree, new_cursor_node);
+
+      search_first_focusable_path (tree_view, &cursor_path,
+                                  (count != -1),
+                                  &new_cursor_tree,
+                                  &new_cursor_node);
+
+      if (cursor_path)
+       gtk_tree_path_free (cursor_path);
+    }
+
   /*
    * If the list has only one item and multi-selection is set then select
    * the row (if not yet selected).
@@ -9665,6 +9780,8 @@ gtk_tree_view_move_cursor_page_up_down (GtkTreeView *tree_view,
   GtkRBNode *cursor_node = NULL;
   GtkTreePath *old_cursor_path = NULL;
   GtkTreePath *cursor_path = NULL;
+  GtkRBTree *start_cursor_tree = NULL;
+  GtkRBNode *start_cursor_node = NULL;
   gint y;
   gint window_y;
   gint vertical_separator;
@@ -9712,7 +9829,33 @@ gtk_tree_view_move_cursor_page_up_down (GtkTreeView *tree_view,
 
   y -= tree_view->priv->cursor_offset;
   cursor_path = _gtk_tree_view_find_path (tree_view, cursor_tree, cursor_node);
-  g_return_if_fail (cursor_path != NULL);
+
+  start_cursor_tree = cursor_tree;
+  start_cursor_node = cursor_node;
+
+  if (! search_first_focusable_path (tree_view, &cursor_path,
+                                    (count != -1),
+                                    &cursor_tree, &cursor_node))
+    {
+      /* It looks like we reached the end of the view without finding
+       * a focusable row.  We will step backwards to find the last
+       * focusable row.
+       */
+      cursor_tree = start_cursor_tree;
+      cursor_node = start_cursor_node;
+      cursor_path = _gtk_tree_view_find_path (tree_view, cursor_tree, cursor_node);
+
+      search_first_focusable_path (tree_view, &cursor_path,
+                                  (count == -1),
+                                  &cursor_tree, &cursor_node);
+    }
+
+  if (!cursor_path)
+    goto cleanup;
+
+  /* update y */
+  y = _gtk_rbtree_node_find_offset (cursor_tree, cursor_node);
+
   gtk_tree_view_real_set_cursor (tree_view, cursor_path, TRUE, FALSE);
 
   y -= window_y;
@@ -9722,6 +9865,7 @@ gtk_tree_view_move_cursor_page_up_down (GtkTreeView *tree_view,
   if (!gtk_tree_path_compare (old_cursor_path, cursor_path))
     gtk_widget_error_bell (GTK_WIDGET (tree_view));
 
+cleanup:
   gtk_tree_path_free (old_cursor_path);
   gtk_tree_path_free (cursor_path);
 }
@@ -9848,6 +9992,11 @@ gtk_tree_view_move_cursor_start_end (GtkTreeView *tree_view,
     {
       while (cursor_node && cursor_node->left != cursor_tree->nil)
        cursor_node = cursor_node->left;
+
+      /* Now go forward to find the first focusable row. */
+      path = _gtk_tree_view_find_path (tree_view, cursor_tree, cursor_node);
+      search_first_focusable_path (tree_view, &path,
+                                  TRUE, &cursor_tree, &cursor_node);
     }
   else
     {
@@ -9862,9 +10011,15 @@ gtk_tree_view_move_cursor_start_end (GtkTreeView *tree_view,
          cursor_node = cursor_tree->root;
        }
       while (1);
+
+      /* Now go backwards to find last focusable row. */
+      path = _gtk_tree_view_find_path (tree_view, cursor_tree, cursor_node);
+      search_first_focusable_path (tree_view, &path,
+                                  FALSE, &cursor_tree, &cursor_node);
     }
 
-  path = _gtk_tree_view_find_path (tree_view, cursor_tree, cursor_node);
+  if (!path)
+    goto cleanup;
 
   if (gtk_tree_path_compare (old_path, path))
     {
@@ -9875,6 +10030,7 @@ gtk_tree_view_move_cursor_start_end (GtkTreeView *tree_view,
       gtk_widget_error_bell (GTK_WIDGET (tree_view));
     }
 
+cleanup:
   gtk_tree_path_free (old_path);
   gtk_tree_path_free (path);
 }
@@ -12352,11 +12508,18 @@ gtk_tree_view_real_set_cursor (GtkTreeView     *tree_view,
     }
 
   gtk_tree_row_reference_free (tree_view->priv->cursor);
+  tree_view->priv->cursor = NULL;
+
+  /* One cannot set the cursor on a separator. */
+  if (!row_is_separator (tree_view, NULL, path))
+    {
+      tree_view->priv->cursor =
+       gtk_tree_row_reference_new_proxy (G_OBJECT (tree_view),
+                                         tree_view->priv->model,
+                                         path);
+      _gtk_tree_view_find_node (tree_view, path, &tree, &node);
+    }
 
-  tree_view->priv->cursor = gtk_tree_row_reference_new_proxy (G_OBJECT (tree_view),
-                                                             tree_view->priv->model,
-                                                             path);
-  _gtk_tree_view_find_node (tree_view, path, &tree, &node);
   if (tree != NULL)
     {
       GtkRBTree *new_tree = NULL;
@@ -13550,12 +13713,7 @@ gtk_tree_view_create_row_drag_icon (GtkTreeView  *tree_view,
                                 path))
     return NULL;
   
-  if (tree_view->priv->row_separator_func)
-    {
-      is_separator = (* tree_view->priv->row_separator_func) (tree_view->priv->model,
-                                                             &iter,
-                                                             tree_view->priv->row_separator_data);
-    }
+  is_separator = row_is_separator (tree_view, &iter, NULL);
 
   cell_offset = x;